iT邦幫忙

2021 iThome 鐵人賽

DAY 12
1
Modern Web

整合 Google 服務的燃料——透過 Google Apps Script (GAS) 加速你的工作速度系列 第 12

D12 - 如何用 Apps Script 寄出客製化的表單並搜集分散在 Google Sheet 中的回應?(二)大幅度客製你的 Google Form

  • 分享至 

  • xImage
  •  

今天的目標

很多時候我們會需要搜集些不同的資料。像是 Marketing 在做大規模但針對不同組織的調查問卷。如果只是三份、五份的問卷要做客製化、統整算還好;但如果是一百份、甚至上千份時,總不能一個個複製了吧。此時就會遇到個問題——

  1. 要如何複製客製化 Google 表單?
  2. 要如何集中很多表單中的資料(回應)?

因為篇幅關係,這邊會拆成三篇來寫,第一篇與第二篇回應 Q1;第三篇回應 Q2。今天這篇是針對 Q1 的第二篇,昨天我們講了怎麼樣複製與簡單客製 Google 表單。而今天來到了第十二天,我們來到目前最進階的操作,用 GAS 完成超·客製表單。一樣先講結論,如果你很急著用,可以直接使用這份 Add-On: Form Publisher,功能非常強大。自己寫的好處是,如果你一天突然要做些高度客製化,那此篇會有幫助。這篇的定位比較像是字典、工具,在你需要用的時候可以來參照。那就讓我們開始吧!


Q1. 我要如何複製客製化 Google 表單?

複製表單有兩種方式。一種是比較簡單的「複製範本」,簡單來說就是針對一個表單複製,然後再改期中的元素。另一種是「從零製作」,這種就比較複雜,因為會是透過 GAS 完整製作表單,會需要比較熟悉 GAS。我們昨天講完方法一,今天則著重在方法二。

方式二:用 GAS 製造表單

如果是比較複雜的表單與設定,舉例來說,有些人需要第一題,有些人第五題要用填空,那要怎麼處理?這個時候可以用 GAS 來製作每份表單。雖然這樣客製化程度很高,但相對製作時間會比前一次更久。果然是充滿取捨的人生。

也給大家看一下這次預計生成的參數們,左邊綠色是我們輸入的參數,右邊橘色是我們預計輸出的參數。

補充:校稿時有朋友問說那個「勾勾」怎麼做,多錄一支影片給大家看

Step 1 開啟 Google Sheet,並串起 GAS

設定步驟跟之前一一樣,從 Google Sheet 中進入 GAS。

Step 2 用 GAS 中生成我們表單

昨天我們有示範如何操作既存的表單,今天我們來看看怎麼生出新的表單。方式極其簡單,就是用 FormApp.create() ,並在括號中輸入表單名稱即可。

function createNewForm(){
  let new_form = FormApp.create('New Form');
}

但,這樣創造有個小問題,就是創造的位置會在根目錄,也就是一開始打開 Google Drive 的位置。那要怎麼移動?目前是需要比較陽春的透過寫 DriveAppmoveTo(folder) 才行。也就是以下示範的程式碼,改編自 JLMosher 的回應

function moveFile(fileId, destinationFolderId) {
  let destinationFolder = DriveApp.getFolderById(destinationFolderId);
  DriveApp.getFileById(fileId).moveTo(destinationFolder);
}

所以原本的表單,使用上就變成了——

function createNewForm(){
  let new_form = FormApp.create('New Form');
  let new_form_id = new_form.getId()
  let destinationFolderId = "your_folder_id_here"
  moveFile(new_form_id, destinationFolderId)
}

那這邊是簡易生成一張表單的方式,接著我們要對每一張表單進行細節的操作,開始囉!

Step 3 用 FormApp 來生出表單的問題

我們要如何在 GAS 內生出問題們?我做了一個簡單方法對照表。

那實際上怎麼用呢,這邊先給大家看完整的程式碼,接著一個個說明。我們以一份要約 onsite interview 的表單為例。

function addNameText(form){
    let text_name = form.addTextItem().setTitle('Name').setRequired(true);
    return text_name
}

function addLunchList(form){
    let list_item = form.addListItem();
    list_item.setTitle('Option for the Lunch')
             .setChoices([
                 list_item.createChoice('Meat'),
                 list_item.createChoice('Vegetarian')
              ]);
    return list_item
}

function addTrafficChoices(form){
     let multipleChoice_item = form.addMultipleChoiceItem();
      multipleChoice_item.setTitle('How do you come to our office?')
                         .setChoices([
                           multipleChoice_item.createChoice('Train'),
                           multipleChoice_item.createChoice('Bus'),
                           multipleChoice_item.createChoice('Drive'),
                           multipleChoice_item.createChoice('Walk')
                         ])
                         .showOtherOption(true);
     return multipleChoice_item
}

function addInterestGridWithValidation(form){
      let grid_item = form.addGridItem();
      grid_item.setTitle('Rate your interests')
               .setRows(['SDE', 'Test Engineer', 'Project Manager'])
               .setColumns([5, 4, 3,2,1])
               .setHelpText("It won't affect your scores in interviewing.");

      let gridValidation = FormApp.createGridValidation()
                                  .setHelpText("Select one item per column.")
                                  .requireLimitOneResponsePerColumn()
                                  .build();

      grid_item.setValidation(gridValidation);
      return grid_item
}

function addSkillCheckbox(form){
      let checkbox_item = form.addCheckboxItem();
      checkbox_item.setTitle('What are your technical skillsets')
                   .setChoices([
                        checkbox_item.createChoice('Python'),
                        checkbox_item.createChoice('JavaScript'),
                        checkbox_item.createChoice('HTML5'),
                        checkbox_item.createChoice('CSS3')
                  ])
                  .showOtherOption(true);
      return checkbox_item
}

function addAvailableDateTime(form){
      let date_time_item = form.addDateTimeItem();
      date_time_item.setTitle('When is your availability?');
      return date_time_item
}

function addSuggestionParagraphText(form){
      let paragraph_text_item = form.addParagraphTextItem();
      paragraph_text_item.setTitle('Any question or suggestion?');
      return paragraph_text_item
}

function addRateScale(form){
      let scale_item = form.addScaleItem();
      scale_item.setTitle('Rate this form')
                .setBounds(1, 5);
      return scale_item
}

function writeForm(curr_form){
      curr_form.setTitle('D12 Form').setDescription('Description of form \nTest for new line');

       let text_name = addNameText(curr_form);
       let list_item = addLunchList(curr_form);
       let multipleChoice_item = addTrafficChoices(curr_form);
       let grid_item = addInterestGridWithValidation(curr_form);
       let checkbox_item = addSkillCheckbox(curr_form);
       let date_time_item = addAvailableDateTime(curr_form);
       let paragraph_text_item = addSuggestionParagraphText(curr_form);
       let scale_item = addRateScale(curr_form);
            
       return curr_form
}

好,那我們一個個來講。順序依照最上面圖的順序。

設定表單標題與敘述(Title / Description)

這邊應該算好理解,針對 form 本身用 setTitle() 設定標題,也透過 setDescription()設定標題下的敘述。

form.setTitle('D12 Form').setDescription('Description of form \n Test for new line');

眼尖的朋友應該有看到我有加入一個 '\n' 在 Description,這是「換行符號」,也就是在敘述段落如 description 時,可以透過加上這符號進行換行。換句話說,如果輸入

// 會出現連續的 123
.setDescription('123')
123

// 會出現分成三行的 1, 2, 3 
.setDescription('1\n2\n3')
1
2
3

補充的是,這邊有三個功能是可以加上的,分別是收到回應的確認訊息、是否允許編輯與是否接受重複回應。

form.setConfirmationMessage('Thanks for responding!')
    .setAllowResponseEdits(true)
    .setAcceptingResponses(false);

對應的回應關係圖如下——

這張表是用中文版 Google Form 在新增問題時的順序,也是我們接下來列點介紹的順序。

設定簡答與段論問題(Text / Paragraph Text)

這邊很簡單地用了 setTextItem() 作為了設定問題的方式,並且針對這個新增的問題用 setTitle() 來給予敘述,並且用 setRequired 來設定必填。

let text_name = form.addTextItem().setTitle('Name').setRequired(true);

而對應的段落也是用 .addParagraphTextItem() 即可。

let paragraph_text_item = form.addParagraphTextItem();
paragraph_text_item.setTitle('Any question or suggestion?');

但如果有時候我們想要加上一些限制,像是至少輸入 100 字,那要怎麼做?這時就要用到 createParagraphTextValidation 來執行。範例程式碼如下——

let paragraphtextValidation = FormApp.createParagraphTextValidation()
                                     .setHelpText(“Answer must be more than 100 characters.”)
                                     .requireTextLengthGreatherThan(100);
paragraph_text_item.setValidation(paragraphtextValidation);

而其總共有六種模式可以設定,分別是...

  • 設定回應需要含有以下 Pattern requireTextContainsPattern(pattern):通常是開頭、結尾需要是特定格式。 e.g. email 的信箱位址。
  • 設定回應不得含有以下 Pattern requireTextDoesNotContainPattern(pattern)
  • 設定回應需要吻合以下 Pattern requireTextMatchesPattern(pattern):通常是字句中需要含有特定關鍵字、模式。 e.g. 含有三碼郵遞區號數字後接上文字
  • 設定回應不得吻合以下 Pattern requireTextDoesNotMatchPattern(pattern)
  • 設定回應長度大於或等於 數字requireTextLengthGreaterThanOrEqualTo(number):e.g. 地址文字中不得含有數字(需用國字中文之類)
  • 設定回應長度小於或等於 數字 requireTextLengthLessThanOrEqualTo(number)

上面的功能中所提到的 Pattern,其實就是 Regex(Regular Expression 正規表示式)。這邊給一個使用的範例。

let paragraphtextValidation = FormApp.createParagraphTextValidation()
                                     .requireTextContainsPattern('[a-zA-Z]')                                     
paragraph_text_item.setValidation(paragraphtextValidation);

上面所寫的這個範例就是,檢查輸入的內容只能是英文大寫 [A-Z] 或小寫 [a-z] 。更詳細 Regex 可以到 regexonelearn regex 學,很詳盡。

設定選擇題(Multiple Choice)

下面這段程式碼,主要是先用 addMultipleChoiceItem() 創造一個 object,並接著用 setChoices()createChoice)_ 來創造選項們,最後設定 showOtherOption() 來讓填答人可以自行輸入「其他」。

let multipleChoice_item = form.addMultipleChoiceItem();
  multipleChoice_item.setTitle('How do you come to our office?')
                     .setChoices([
                       multipleChoice_item.createChoice('Train'),
                       multipleChoice_item.createChoice('Bus'),
                       multipleChoice_item.createChoice('Drive'),
                       multipleChoice_item.createChoice('Walk')
                     ])
                     .showOtherOption(true);

設定核取方塊(Checkbox)

方式跟設定選擇題幾乎一樣,只差在是用 addCheckboxItem() 來建造。

let checkbox_item = form.addCheckboxItem();
checkbox_item.setTitle('What are your technical skillsets')
             .setChoices([
                    checkbox_item.createChoice('Python'),
                    checkbox_item.createChoice('JavaScript'),
                    checkbox_item.createChoice('HTML5'),
                    checkbox_item.createChoice('CSS3')
             ])
             .showOtherOption(true);

額外有 CheckboxValidationBuilder 可以建造驗證程序,方式包括

  • 至少填入幾個選項 requireSelectAtLeast()
  • 至多填入幾個選項 requireSelectAtMost()
  • 剛好填入幾個選項 requireSelectExactly()

提供官方範例給大家參考~

var checkBoxValidation = FormApp.createCheckboxValidation()
                                .setHelpText(“Select two condiments.”)
                                .requireSelectExactly(2)
                                .build();
checkBoxItem.setValidation(checkBoxValidation);

設定下拉式選單(List)

透過 addListItem() 來建置即可,比較沒看到特別好玩的部分

 let list_item = form.addListItem();
  list_item.setTitle('Option for the Lunch')
           .setChoices([
                list_item.createChoice('Meat'),
                list_item.createChoice('Vegetarian')
            ]);

設定線性刻度(Scale)

透過 addScaleItem() 來建置。基本上是輸入數值。那身為中文使用者,會很想問說,那怎麼樣輸入中文?這時就要透過 setLabels('Bad', 'Good') 的方式。

let scale_item = form.addScaleItem();
scale_item.setTitle('Rate this form')
          .setBounds(1, 5)
          .setBounds('Bad', 'Good');

設定單選方格(Grid) 和 核取方塊格(Checkbox Grid)

藉由 addGridItem 來新增。

let grid_item = form.addGridItem();
  grid_item.setTitle('Rate your interests')
           .setRows(['SDE', 'Test Engineer', 'Project Manager'])
           .setColumns([5, 4, 3,2,1])
           .setHelpText("It won't affect your scores in interviewing.");

且一樣有 GridValidationBuilder 可以建立。但方式只有一種,也就是限制每一直欄都只能有一個被填入:requireLimitOneResponsePerColumn()。程式碼如下。

let gridValidation = FormApp.createGridValidation()
                              .setHelpText("Select one item per column.")
                              .requireLimitOneResponsePerColumn()
                              .build();

  grid_item.setValidation(gridValidation);

那會想問,如果我想設定的是每個橫的行,都必須要填入一個選項呢?就單純用 .setRequired(true) 即可做到了。

那至於「核取方塊格」呢?因為方式都一樣,所以可以單純地把 addGridItem 換成 addCheckboxGridItem。兩者的 validation method 也都都是只有一種,但核取方塊格需要將 GridValidationBuilder 換成 CheckboxGridValidationBuilder() 就是。

設定日期與時間(addDateTimeItem)

日期與時間也相對單純,用 addDateTimeItem() 即可。

let date_time_item = form.addDateTimeItem();
date_time_item.setTitle('When is your availability?');

Step 3 讀取 Google Sheet 裡面的資料來客製 Google Form

好,但我們的重點是客製化表單,要怎麼樣將原本的做表格變客製化呢?這邊先用個簡單的方式。

function writeForm(){
  let data = readData();
  let new_forms_id_arr = [];
  for(row_data of data){
   let form_name = row_data[0];
   let form_description = row_data[1];
   
   let curr_form = FormApp.create(form_name);
   let curr_form_id = curr_form.getId()
   new_forms_id_arr.push([curr_form_id]);
   moveFile(curr_form_id, target_folder_ID)
   curr_form.setTitle(form_name).setDescription(form_description);
   
   let question_function_list = [addNameText,
                                 addLunchList,
                                 addTrafficChoices,        
                                 addInterestGridWithValidation,
                                 addSkillCheckbox,
                                 addAvailableDateTime,
                                 addSuggestionParagraphText,
                                 ]
     
    for(let i = 2; i< row_data.length; i++){
        if(row_data[i] == true){
            question_function_list[i-2](curr_form);
        }
    }
    
   // add general item
   addRateScale(curr_form);
  }
  writeData(new_forms_id_arr)
}

裡頭要用到的功能上方都有寫,可以直接複製喔!

Step 4 將創造後的表單 ID 寫回 Google Sheet

其實已經偷偷寫在上面的 Code 裡面了,請看 Step 3 最後 writeData() 的部分。

完整執行畫面——

回對我們的任務表,確認 Ben 表單中是沒有「下拉選單」午餐編號的。

任務完成!


好,我們總算完成了(落淚),雖然昨日 D11 介紹的第一種方式比較簡單,但實際上想彈性運用,我們會需要很多 D12 的內容。但,如果我們今天真的生了 100 份表單,那要怎麼樣統一回應?總不能慢慢搜集吧。這時候就會需要看我們的 D13 了。

不知不覺就寫了一整天...Orz,希望大家喜歡。一樣提醒,用FormApp.create()來創造表單是有 Quota 限制——每天不超過 250 份。如果還有問題,透過留言之外,也可以到 Facebook Group,想開很久這次鐵人賽才真的開起來哈哈哈,歡迎來當 Founding Member。如果不想錯過可以訂閱按讚小鈴鐺(?),也歡迎留言跟我說你還想知道什麼做法/主題。我們明天見。


上一篇
D11 - 如何用 Apps Script 寄出客製化的表單並搜集分散在 Google Sheet 中的回應?(ㄧ)複製並客製你的 Google Form
下一篇
如何用 Apps Script 寄出客製化的 Google 表單並搜集分散在 Google Sheet 中的回應?(三)一次搜集很多 Google Form 內的回應
系列文
整合 Google 服務的燃料——透過 Google Apps Script (GAS) 加速你的工作速度30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言